blob: 0ba51c49bd70c4e621d0ee0a3237c7ce5e27f763 [file] [log] [blame]
Deniz Türkoglueb78b602012-05-07 14:02:36 -07001Gerrit Code Review - Plugin Development
2=======================================
3
Edwin Kempinaf275322012-07-16 11:04:01 +02004The Gerrit server functionality can be extended by installing plugins.
5This page describes how plugins for Gerrit can be developed.
6
7Depending on how tightly the extension code is coupled with the Gerrit
8server code, there is a distinction between `plugins` and `extensions`.
9
Edwin Kempinf5a77332012-07-18 11:17:53 +020010[[plugin]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020011A `plugin` in Gerrit is tightly coupled code that runs in the same
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070012JVM as Gerrit. It has full access to all server internals. Plugins
13are tightly coupled to a specific major.minor server version and
14may require source code changes to compile against a different
15server version.
16
Edwin Kempinf5a77332012-07-18 11:17:53 +020017[[extension]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020018An `extension` in Gerrit runs inside of the same JVM as Gerrit
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070019in the same way as a plugin, but has limited visibility to the
Edwin Kempinfd19bfb2012-07-16 10:44:17 +020020server's internals. The limited visibility reduces the extension's
21dependencies, enabling it to be compatible across a wider range
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070022of server versions.
23
24Most of this documentation refers to either type as a plugin.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070025
Edwin Kempinf878c4b2012-07-18 09:34:25 +020026[[getting-started]]
27Getting started
28---------------
Deniz Türkoglueb78b602012-05-07 14:02:36 -070029
Edwin Kempinf878c4b2012-07-18 09:34:25 +020030To get started with the development of a plugin there are two
31recommended ways:
Dave Borowitz5cc8f662012-05-21 09:51:36 -070032
Edwin Kempinf878c4b2012-07-18 09:34:25 +020033. use the Gerrit Plugin Maven archetype to create a new plugin project:
34+
35With the Gerrit Plugin Maven archetype you can create a skeleton for a
36plugin project.
37+
38----
39mvn archetype:generate -DarchetypeGroupId=com.google.gerrit \
40 -DarchetypeArtifactId=gerrit-plugin-archetype \
41 -DarchetypeVersion=2.5-SNAPSHOT \
42 -DgroupId=com.google.gerrit \
43 -DartifactId=testPlugin
44----
45+
46Maven will ask for additional properties and then create the plugin in
47the current directory. To change the default property values answer 'n'
48when Maven asks to confirm the properties configuration. It will then
49ask again for all properties including those with predefined default
50values.
51
David Pursehouse2cf0cb52013-08-27 16:09:53 +090052. clone the sample plugin:
Edwin Kempinf878c4b2012-07-18 09:34:25 +020053+
David Pursehouse2cf0cb52013-08-27 16:09:53 +090054This is a project that demonstrates the various features of the
55plugin API. It can be taken as an example to develop an own plugin.
Edwin Kempinf878c4b2012-07-18 09:34:25 +020056+
Dave Borowitz5cc8f662012-05-21 09:51:36 -070057----
David Pursehouse2cf0cb52013-08-27 16:09:53 +090058$ git clone https://gerrit.googlesource.com/plugins/cookbook-plugin
Dave Borowitz5cc8f662012-05-21 09:51:36 -070059----
Edwin Kempinf878c4b2012-07-18 09:34:25 +020060+
61When starting from this example one should take care to adapt the
62`Gerrit-ApiVersion` in the `pom.xml` to the version of Gerrit for which
63the plugin is developed. If the plugin is developed for a released
64Gerrit version (no `SNAPSHOT` version) then the URL for the
65`gerrit-api-repository` in the `pom.xml` needs to be changed to
Shawn Pearced5005002013-06-21 11:01:45 -070066`https://gerrit-api.storage.googleapis.com/release/`.
Dave Borowitz5cc8f662012-05-21 09:51:36 -070067
Edwin Kempinf878c4b2012-07-18 09:34:25 +020068[[API]]
69API
70---
71
72There are two different API formats offered against which plugins can
73be developed:
Deniz Türkoglueb78b602012-05-07 14:02:36 -070074
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070075gerrit-extension-api.jar::
76 A stable but thin interface. Suitable for extensions that need
77 to be notified of events, but do not require tight coupling to
78 the internals of Gerrit. Extensions built against this API can
79 expect to be binary compatible across a wide range of server
80 versions.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070081
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070082gerrit-plugin-api.jar::
83 The complete internals of the Gerrit server, permitting a
84 plugin to tightly couple itself and provide additional
85 functionality that is not possible as an extension. Plugins
86 built against this API are expected to break at the source
87 code level between every major.minor Gerrit release. A plugin
88 that compiles against 2.5 will probably need source code level
89 changes to work with 2.6, 2.7, and so on.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070090
91Manifest
92--------
93
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070094Plugins may provide optional description information with standard
95manifest fields:
Nasser Grainawie033b262012-05-09 17:54:21 -070096
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070097====
98 Implementation-Title: Example plugin showing examples
99 Implementation-Version: 1.0
100 Implementation-Vendor: Example, Inc.
101 Implementation-URL: http://example.com/opensource/plugin-foo/
102====
Nasser Grainawie033b262012-05-09 17:54:21 -0700103
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700104ApiType
105~~~~~~~
Nasser Grainawie033b262012-05-09 17:54:21 -0700106
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700107Plugins using the tightly coupled `gerrit-plugin-api.jar` must
108declare this API dependency in the manifest to gain access to server
Edwin Kempin948de0f2012-07-16 10:34:35 +0200109internals. If no `Gerrit-ApiType` is specified the stable `extension`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700110API will be assumed. This may cause ClassNotFoundExceptions when
111loading a plugin that needs the plugin API.
Nasser Grainawie033b262012-05-09 17:54:21 -0700112
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700113====
114 Gerrit-ApiType: plugin
115====
116
117Explicit Registration
118~~~~~~~~~~~~~~~~~~~~~
119
120Plugins that use explicit Guice registration must name the Guice
121modules in the manifest. Up to three modules can be named in the
Edwin Kempin948de0f2012-07-16 10:34:35 +0200122manifest. `Gerrit-Module` supplies bindings to the core server;
123`Gerrit-SshModule` supplies SSH commands to the SSH server (if
124enabled); `Gerrit-HttpModule` supplies servlets and filters to the HTTP
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700125server (if enabled). If no modules are named automatic registration
126will be performed by scanning all classes in the plugin JAR for
127`@Listen` and `@Export("")` annotations.
128
129====
130 Gerrit-Module: tld.example.project.CoreModuleClassName
131 Gerrit-SshModule: tld.example.project.SshModuleClassName
132 Gerrit-HttpModule: tld.example.project.HttpModuleClassName
133====
134
Edwin Kempinf7295742012-07-16 15:03:46 +0200135[[reload_method]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700136Reload Method
137~~~~~~~~~~~~~
138
139If a plugin holds an exclusive resource that must be released before
140loading the plugin again (for example listening on a network port or
Edwin Kempin948de0f2012-07-16 10:34:35 +0200141acquiring a file lock) the manifest must declare `Gerrit-ReloadMode`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700142to be `restart`. Otherwise the preferred method of `reload` will
143be used, as it enables the server to hot-patch an updated plugin
144with no down time.
145
146====
147 Gerrit-ReloadMode: restart
148====
149
150In either mode ('restart' or 'reload') any plugin or extension can
151be updated without restarting the Gerrit server. The difference is
152how Gerrit handles the upgrade:
153
154restart::
155 The old plugin is completely stopped. All registrations of SSH
156 commands and HTTP servlets are removed. All registrations of any
157 extension points are removed. All registered LifecycleListeners
158 have their `stop()` method invoked in reverse order. The new
159 plugin is started, and registrations are made from the new
160 plugin. There is a brief window where neither the old nor the
161 new plugin is connected to the server. This means SSH commands
162 and HTTP servlets will return not found errors, and the plugin
163 will not be notified of events that occurred during the restart.
164
165reload::
166 The new plugin is started. Its LifecycleListeners are permitted
167 to perform their `start()` methods. All SSH and HTTP registrations
168 are atomically swapped out from the old plugin to the new plugin,
169 ensuring the server never returns a not found error. All extension
170 point listeners are atomically swapped out from the old plugin to
171 the new plugin, ensuring no events are missed (however some events
172 may still route to the old plugin if the swap wasn't complete yet).
173 The old plugin is stopped.
174
Edwin Kempinf7295742012-07-16 15:03:46 +0200175To reload/restart a plugin the link:cmd-plugin-reload.html[plugin reload]
176command can be used.
177
Luca Milanesio737285d2012-09-25 14:26:43 +0100178[[init_step]]
179Init step
180~~~~~~~~~
181
182Plugins can contribute their own "init step" during the Gerrit init
183wizard. This is useful for guiding the Gerrit administrator through
184the settings needed by the plugin to work propertly.
185
186For instance plugins to integrate Jira issues to Gerrit changes may
187contribute their own "init step" to allow configuring the Jira URL,
188credentials and possibly verify connectivity to validate them.
189
190====
191 Gerrit-InitStep: tld.example.project.MyInitStep
192====
193
194MyInitStep needs to follow the standard Gerrit InitStep syntax
David Pursehouse92463562013-06-24 10:16:28 +0900195and behavior: writing to the console using the injected ConsoleUI
Luca Milanesio737285d2012-09-25 14:26:43 +0100196and accessing / changing configuration settings using Section.Factory.
197
198In addition to the standard Gerrit init injections, plugins receive
199the @PluginName String injection containing their own plugin name.
200
201Bear in mind that the Plugin's InitStep class will be loaded but
202the standard Gerrit runtime environment is not available and the plugin's
203own Guice modules were not initialized.
204This means the InitStep for a plugin is not executed in the same way that
205the plugin executes within the server, and may mean a plugin author cannot
206trivially reuse runtime code during init.
207
208For instance a plugin that wants to verify connectivity may need to statically
209call the constructor of their connection class, passing in values obtained
210from the Section.Factory rather than from an injected Config object.
211
212Plugins InitStep are executing during the "Gerrit Plugin init" phase, after
213the extraction of the plugins embedded in Gerrit.war into $GERRIT_SITE/plugins
214and before the DB Schema initialization or upgrade.
215Plugins InitStep cannot refer to Gerrit DB Schema or any other Gerrit runtime
216objects injected at startup.
217
David Pursehouse68153d72013-09-04 10:09:17 +0900218[source,java]
219----
220public class MyInitStep implements InitStep {
221 private final ConsoleUI ui;
222 private final Section.Factory sections;
223 private final String pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100224
David Pursehouse68153d72013-09-04 10:09:17 +0900225 @Inject
226 public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
227 this.ui = ui;
228 this.sections = sections;
229 this.pluginName = pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100230 }
David Pursehouse68153d72013-09-04 10:09:17 +0900231
232 @Override
233 public void run() throws Exception {
234 ui.header("\nMy plugin");
235
236 Section mySection = getSection("myplugin", null);
237 mySection.string("Link name", "linkname", "MyLink");
238 }
239}
240----
Luca Milanesio737285d2012-09-25 14:26:43 +0100241
Edwin Kempinf5a77332012-07-18 11:17:53 +0200242[[classpath]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700243Classpath
244---------
245
246Each plugin is loaded into its own ClassLoader, isolating plugins
247from each other. A plugin or extension inherits the Java runtime
248and the Gerrit API chosen by `Gerrit-ApiType` (extension or plugin)
249from the hosting server.
250
251Plugins are loaded from a single JAR file. If a plugin needs
252additional libraries, it must include those dependencies within
253its own JAR. Plugins built using Maven may be able to use the
254link:http://maven.apache.org/plugins/maven-shade-plugin/[shade plugin]
255to package additional dependencies. Relocating (or renaming) classes
256should not be necessary due to the ClassLoader isolation.
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700257
Edwin Kempinf5a77332012-07-18 11:17:53 +0200258[[ssh]]
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700259SSH Commands
260------------
261
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700262Plugins may provide commands that can be accessed through the SSH
263interface (extensions do not have this option).
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700264
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700265Command implementations must extend the base class SshCommand:
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700266
David Pursehouse68153d72013-09-04 10:09:17 +0900267[source,java]
268----
269import com.google.gerrit.sshd.SshCommand;
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700270
David Pursehouse68153d72013-09-04 10:09:17 +0900271class PrintHello extends SshCommand {
272 protected abstract void run() {
273 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700274 }
David Pursehouse68153d72013-09-04 10:09:17 +0900275}
276----
Nasser Grainawie033b262012-05-09 17:54:21 -0700277
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700278If no Guice modules are declared in the manifest, SSH commands may
Edwin Kempin948de0f2012-07-16 10:34:35 +0200279use auto-registration by providing an `@Export` annotation:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700280
David Pursehouse68153d72013-09-04 10:09:17 +0900281[source,java]
282----
283import com.google.gerrit.extensions.annotations.Export;
284import com.google.gerrit.sshd.SshCommand;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700285
David Pursehouse68153d72013-09-04 10:09:17 +0900286@Export("print")
287class PrintHello extends SshCommand {
288 protected abstract void run() {
289 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700290 }
David Pursehouse68153d72013-09-04 10:09:17 +0900291}
292----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700293
294If explicit registration is being used, a Guice module must be
295supplied to register the SSH command and declared in the manifest
296with the `Gerrit-SshModule` attribute:
297
David Pursehouse68153d72013-09-04 10:09:17 +0900298[source,java]
299----
300import com.google.gerrit.sshd.PluginCommandModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700301
David Pursehouse68153d72013-09-04 10:09:17 +0900302class MyCommands extends PluginCommandModule {
303 protected void configureCommands() {
304 command("print").to(PrintHello.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700305 }
David Pursehouse68153d72013-09-04 10:09:17 +0900306}
307----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700308
309For a plugin installed as name `helloworld`, the command implemented
310by PrintHello class will be available to users as:
311
312----
Keunhong Parka09a6f12012-07-10 14:45:02 -0600313$ ssh -p 29418 review.example.com helloworld print
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700314----
315
David Ostrovsky7066cc02013-06-15 14:46:23 +0200316[[capabilities]]
317Plugin Owned Capabilities
318-------------------------
319
320Plugins may provide their own capabilities and restrict usage of SSH
321commands to the users who are granted those capabilities.
322
323Plugins define the capabilities by overriding the `CapabilityDefinition`
324abstract class:
325
David Pursehouse68153d72013-09-04 10:09:17 +0900326[source,java]
327----
328public class PrintHelloCapability extends CapabilityDefinition {
329 @Override
330 public String getDescription() {
331 return "Print Hello";
David Ostrovsky7066cc02013-06-15 14:46:23 +0200332 }
David Pursehouse68153d72013-09-04 10:09:17 +0900333}
334----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200335
David Ostrovskyf86bae52013-09-01 09:10:39 +0200336If no Guice modules are declared in the manifest, UI actions may
David Ostrovsky7066cc02013-06-15 14:46:23 +0200337use auto-registration by providing an `@Export` annotation:
338
David Pursehouse68153d72013-09-04 10:09:17 +0900339[source,java]
340----
341@Export("printHello")
342public class PrintHelloCapability extends CapabilityDefinition {
David Ostrovsky7066cc02013-06-15 14:46:23 +0200343 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900344}
345----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200346
347Otherwise the capability must be bound in a plugin module:
348
David Pursehouse68153d72013-09-04 10:09:17 +0900349[source,java]
350----
351public class HelloWorldModule extends AbstractModule {
352 @Override
353 protected void configure() {
354 bind(CapabilityDefinition.class)
355 .annotatedWith(Exports.named("printHello"))
356 .to(PrintHelloCapability.class);
David Ostrovsky7066cc02013-06-15 14:46:23 +0200357 }
David Pursehouse68153d72013-09-04 10:09:17 +0900358}
359----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200360
361With a plugin-owned capability defined in this way, it is possible to restrict
David Ostrovskyf86bae52013-09-01 09:10:39 +0200362usage of an SSH command or `UiAction` to members of the group that were granted
David Ostrovsky7066cc02013-06-15 14:46:23 +0200363this capability in the usual way, using the `RequiresCapability` annotation:
364
David Pursehouse68153d72013-09-04 10:09:17 +0900365[source,java]
366----
367@RequiresCapability("printHello")
368@CommandMetaData(name="print", description="Print greeting in different languages")
369public final class PrintHelloWorldCommand extends SshCommand {
David Ostrovsky7066cc02013-06-15 14:46:23 +0200370 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900371}
372----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200373
David Ostrovskyf86bae52013-09-01 09:10:39 +0200374Or with `UiAction`:
David Ostrovsky7066cc02013-06-15 14:46:23 +0200375
David Pursehouse68153d72013-09-04 10:09:17 +0900376[source,java]
377----
378@RequiresCapability("printHello")
379public class SayHelloAction extends UiAction<RevisionResource>
380 implements RestModifyView<RevisionResource, SayHelloAction.Input> {
David Ostrovsky7066cc02013-06-15 14:46:23 +0200381 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900382}
383----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200384
385Capability scope was introduced to differentiate between plugin-owned
David Pursehousebf053342013-09-05 14:55:29 +0900386capabilities and core capabilities. Per default the scope of the
387`@RequiresCapability` annotation is `CapabilityScope.CONTEXT`, that means:
388
David Ostrovsky7066cc02013-06-15 14:46:23 +0200389* when `@RequiresCapability` is used within a plugin the scope of the
390capability is assumed to be that plugin.
David Pursehousebf053342013-09-05 14:55:29 +0900391
David Ostrovsky7066cc02013-06-15 14:46:23 +0200392* If `@RequiresCapability` is used within the core Gerrit Code Review server
393(and thus is outside of a plugin) the scope is the core server and will use
394the `GlobalCapability` known to Gerrit Code Review server.
395
396If a plugin needs to use a core capability name (e.g. "administrateServer")
397this can be specified by setting `scope = CapabilityScope.CORE`:
398
David Pursehouse68153d72013-09-04 10:09:17 +0900399[source,java]
400----
401@RequiresCapability(value = "administrateServer", scope =
402 CapabilityScope.CORE)
David Ostrovsky7066cc02013-06-15 14:46:23 +0200403 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900404----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200405
David Ostrovskyf86bae52013-09-01 09:10:39 +0200406[[ui_extension]]
407UI Extension
408------------
409
410Plugins can contribute their own UI commands on core Gerrit pages.
411This is useful for workflow customization or exposing plugin functionality
412through the UI in addition to SSH commands and the REST API.
413
414For instance a plugin to integrate Jira with Gerrit changes may contribute its
415own "File bug" button to allow filing a bug from the change page or plugins to
416integrate continuous integration systems may contribute a "Schedule" button to
417allow a CI build to be scheduled manually from the patch set panel.
418
419Two different places on core Gerrit pages are currently supported:
420
421* Change screen
422* Project info screen
423
424Plugins contribute UI actions by implementing the `UiAction` interface:
425
David Pursehouse68153d72013-09-04 10:09:17 +0900426[source,java]
427----
428@RequiresCapability("printHello")
429class HelloWorldAction implements UiAction<RevisionResource>,
430 RestModifyView<RevisionResource, HelloWorldAction.Input> {
431 static class Input {
432 boolean french;
433 String message;
David Ostrovskyf86bae52013-09-01 09:10:39 +0200434 }
David Pursehouse68153d72013-09-04 10:09:17 +0900435
436 private Provider<CurrentUser> user;
437
438 @Inject
439 HelloWorldAction(Provider<CurrentUser> user) {
440 this.user = user;
441 }
442
443 @Override
444 public String apply(RevisionResource rev, Input input) {
445 final String greeting = input.french
446 ? "Bonjour"
447 : "Hello";
448 return String.format("%s %s from change %s, patch set %d!",
449 greeting,
450 Strings.isNullOrEmpty(input.message)
451 ? Objects.firstNonNull(user.get().getUserName(), "world")
452 : input.message,
453 rev.getChange().getId().toString(),
454 rev.getPatchSet().getPatchSetId());
455 }
456
457 @Override
458 public Description getDescription(
459 RevisionResource resource) {
460 return new Description()
461 .setLabel("Say hello")
462 .setTitle("Say hello in different languages");
463 }
464}
465----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200466
467`UiAction` must be bound in a plugin module:
468
David Pursehouse68153d72013-09-04 10:09:17 +0900469[source,java]
470----
471public class Module extends AbstractModule {
472 @Override
473 protected void configure() {
474 install(new RestApiModule() {
475 @Override
476 protected void configure() {
477 post(REVISION_KIND, "say-hello")
478 .to(HelloWorldAction.class);
479 }
480 });
David Ostrovskyf86bae52013-09-01 09:10:39 +0200481 }
David Pursehouse68153d72013-09-04 10:09:17 +0900482}
483----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200484
485The module above must be declared in pom.xml for Maven driven plugins:
486
David Pursehouse68153d72013-09-04 10:09:17 +0900487[source,xml]
488----
489<manifestEntries>
490 <Gerrit-Module>com.googlesource.gerrit.plugins.cookbook.Module</Gerrit-Module>
491</manifestEntries>
492----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200493
494or in the BUCK configuration file for Buck driven plugins:
495
David Pursehouse68153d72013-09-04 10:09:17 +0900496[source,python]
497----
498manifest_entries = [
499 'Gerrit-Module: com.googlesource.gerrit.plugins.cookbook.Module',
500]
501----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200502
503In some use cases more user input must be gathered, for that `UiAction` can be
504combined with the JavaScript API. This would display a small popup near the
505activation button to gather additional input from the user. The JS file is
506typically put in the `static` folder within the plugin's directory:
507
David Pursehouse68153d72013-09-04 10:09:17 +0900508[source,javascript]
509----
510Gerrit.install(function(self) {
511 function onSayHello(c) {
512 var f = c.textfield();
513 var t = c.checkbox();
514 var b = c.button('Say hello', {onclick: function(){
515 c.call(
516 {message: f.value, french: t.checked},
517 function(r) {
518 c.hide();
519 window.alert(r);
520 c.refresh();
521 });
522 }});
523 c.popup(c.div(
524 c.prependLabel('Greeting message', f),
525 c.br(),
526 c.label(t, 'french'),
527 c.br(),
528 b));
529 f.focus();
530 }
531 self.onAction('revision', 'say-hello', onSayHello);
532});
533----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200534
535The JS module must be exposed as a `WebUiPlugin` and bound as
536an HTTP Module:
537
David Pursehouse68153d72013-09-04 10:09:17 +0900538[source,java]
539----
540public class HttpModule extends HttpPluginModule {
541 @Override
542 protected void configureServlets() {
543 DynamicSet.bind(binder(), WebUiPlugin.class)
544 .toInstance(new JavaScriptPlugin("hello.js"));
David Ostrovskyf86bae52013-09-01 09:10:39 +0200545 }
David Pursehouse68153d72013-09-04 10:09:17 +0900546}
547----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200548
549The HTTP module above must be declared in pom.xml for Maven driven plugins:
550
David Pursehouse68153d72013-09-04 10:09:17 +0900551[source,xml]
552----
553<manifestEntries>
554 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.cookbook.HttpModule</Gerrit-HttpModule>
555</manifestEntries>
556----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200557
558or in the BUCK configuration file for Buck driven plugins
559
David Pursehouse68153d72013-09-04 10:09:17 +0900560[source,python]
561----
562manifest_entries = [
563 'Gerrit-HttpModule: com.googlesource.gerrit.plugins.cookbook.HttpModule',
564]
565----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200566
567If `UiAction` is annotated with the `@RequiresCapability` annotation, then the
568capability check is done during the `UiAction` gathering, so the plugin author
569doesn't have to set `UiAction.Description.setVisible()` explicitly in this
570case.
571
572The following prerequisities must be met, to satisfy the capability check:
573
574* user is authenticated
575* user is a member of the Administrators group, or
576* user is a member of a group which has the required capability
577
578The `apply` method is called when the button is clicked. If `UiAction` is
579combined with JavaScript API (its own JavaScript function is provided),
580then a popup dialog is normally opened to gather additional user input.
581A new button is placed on the popup dialog to actually send the request.
582
583Every `UiAction` exposes a REST API endpoint. The endpoint from the example above
584can be accessed from any REST client, i. e.:
585
586====
587 curl -X POST -H "Content-Type: application/json" \
588 -d '{message: "François", french: true}' \
589 --digest --user joe:secret \
590 http://host:port/a/changes/1/revisions/1/cookbook~say-hello
591 "Bonjour François from change 1, patch set 1!"
592====
593
Edwin Kempinf5a77332012-07-18 11:17:53 +0200594[[http]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700595HTTP Servlets
596-------------
597
598Plugins or extensions may register additional HTTP servlets, and
599wrap them with HTTP filters.
600
601Servlets may use auto-registration to declare the URL they handle:
602
David Pursehouse68153d72013-09-04 10:09:17 +0900603[source,java]
604----
605import com.google.gerrit.extensions.annotations.Export;
606import com.google.inject.Singleton;
607import javax.servlet.http.HttpServlet;
608import javax.servlet.http.HttpServletRequest;
609import javax.servlet.http.HttpServletResponse;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700610
David Pursehouse68153d72013-09-04 10:09:17 +0900611@Export("/print")
612@Singleton
613class HelloServlet extends HttpServlet {
614 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
615 res.setContentType("text/plain");
616 res.setCharacterEncoding("UTF-8");
617 res.getWriter().write("Hello");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700618 }
David Pursehouse68153d72013-09-04 10:09:17 +0900619}
620----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700621
Edwin Kempin8aa650f2012-07-18 11:25:48 +0200622The auto registration only works for standard servlet mappings like
623`/foo` or `/foo/*`. Regex style bindings must use a Guice ServletModule
624to register the HTTP servlets and declare it explicitly in the manifest
625with the `Gerrit-HttpModule` attribute:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700626
David Pursehouse68153d72013-09-04 10:09:17 +0900627[source,java]
628----
629import com.google.inject.servlet.ServletModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700630
David Pursehouse68153d72013-09-04 10:09:17 +0900631class MyWebUrls extends ServletModule {
632 protected void configureServlets() {
633 serve("/print").with(HelloServlet.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700634 }
David Pursehouse68153d72013-09-04 10:09:17 +0900635}
636----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700637
638For a plugin installed as name `helloworld`, the servlet implemented
639by HelloServlet class will be available to users as:
640
641----
642$ curl http://review.example.com/plugins/helloworld/print
643----
Nasser Grainawie033b262012-05-09 17:54:21 -0700644
Edwin Kempinf5a77332012-07-18 11:17:53 +0200645[[data-directory]]
Edwin Kempin41f63912012-07-17 12:33:55 +0200646Data Directory
647--------------
648
649Plugins can request a data directory with a `@PluginData` File
650dependency. A data directory will be created automatically by the
651server in `$site_path/data/$plugin_name` and passed to the plugin.
652
653Plugins can use this to store any data they want.
654
David Pursehouse68153d72013-09-04 10:09:17 +0900655[source,java]
656----
657@Inject
658MyType(@PluginData java.io.File myDir) {
659 new FileInputStream(new File(myDir, "my.config"));
660}
661----
Edwin Kempin41f63912012-07-17 12:33:55 +0200662
Edwin Kempinf5a77332012-07-18 11:17:53 +0200663[[documentation]]
Nasser Grainawie033b262012-05-09 17:54:21 -0700664Documentation
665-------------
666
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700667If a plugin does not register a filter or servlet to handle URLs
668`/Documentation/*` or `/static/*`, the core Gerrit server will
669automatically export these resources over HTTP from the plugin JAR.
670
David Pursehouse6853b5a2013-07-10 11:38:03 +0900671Static resources under the `static/` directory in the JAR will be
Dave Borowitzb893ac82013-03-27 10:03:55 -0400672available as `/plugins/helloworld/static/resource`. This prefix is
673configurable by setting the `Gerrit-HttpStaticPrefix` attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700674
David Pursehouse6853b5a2013-07-10 11:38:03 +0900675Documentation files under the `Documentation/` directory in the JAR
Dave Borowitzb893ac82013-03-27 10:03:55 -0400676will be available as `/plugins/helloworld/Documentation/resource`. This
677prefix is configurable by setting the `Gerrit-HttpDocumentationPrefix`
678attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700679
680Documentation may be written in
681link:http://daringfireball.net/projects/markdown/[Markdown] style
682if the file name ends with `.md`. Gerrit will automatically convert
683Markdown to HTML if accessed with extension `.html`.
Nasser Grainawie033b262012-05-09 17:54:21 -0700684
Edwin Kempinf5a77332012-07-18 11:17:53 +0200685[[macros]]
Edwin Kempinc78777d2012-07-16 15:55:11 +0200686Within the Markdown documentation files macros can be used that allow
687to write documentation with reasonably accurate examples that adjust
688automatically based on the installation.
689
690The following macros are supported:
691
692[width="40%",options="header"]
693|===================================================
694|Macro | Replacement
695|@PLUGIN@ | name of the plugin
696|@URL@ | Gerrit Web URL
697|@SSH_HOST@ | SSH Host
698|@SSH_PORT@ | SSH Port
699|===================================================
700
701The macros will be replaced when the documentation files are rendered
702from Markdown to HTML.
703
704Macros that start with `\` such as `\@KEEP@` will render as `@KEEP@`
705even if there is an expansion for `KEEP` in the future.
706
Edwin Kempinf5a77332012-07-18 11:17:53 +0200707[[auto-index]]
Shawn O. Pearce795167c2012-05-12 11:20:18 -0700708Automatic Index
709~~~~~~~~~~~~~~~
710
711If a plugin does not handle its `/` URL itself, Gerrit will
712redirect clients to the plugin's `/Documentation/index.html`.
713Requests for `/Documentation/` (bare directory) will also redirect
714to `/Documentation/index.html`.
715
716If neither resource `Documentation/index.html` or
717`Documentation/index.md` exists in the plugin JAR, Gerrit will
718automatically generate an index page for the plugin's documentation
719tree by scanning every `*.md` and `*.html` file in the Documentation/
720directory.
721
722For any discovered Markdown (`*.md`) file, Gerrit will parse the
723header of the file and extract the first level one title. This
724title text will be used as display text for a link to the HTML
725version of the page.
726
727For any discovered HTML (`*.html`) file, Gerrit will use the name
728of the file, minus the `*.html` extension, as the link text. Any
729hyphens in the file name will be replaced with spaces.
730
David Pursehouse6853b5a2013-07-10 11:38:03 +0900731If a discovered file is named `about.md` or `about.html`, its
732content will be inserted in an 'About' section at the top of the
733auto-generated index page. If both `about.md` and `about.html`
734exist, only the first discovered file will be used.
735
Shawn O. Pearce795167c2012-05-12 11:20:18 -0700736If a discovered file name beings with `cmd-` it will be clustered
David Pursehouse6853b5a2013-07-10 11:38:03 +0900737into a 'Commands' section of the generated index page.
738
David Pursehousefe529152013-08-14 16:35:06 +0900739If a discovered file name beings with `servlet-` it will be clustered
740into a 'Servlets' section of the generated index page.
741
742If a discovered file name beings with `rest-api-` it will be clustered
743into a 'REST APIs' section of the generated index page.
744
David Pursehouse6853b5a2013-07-10 11:38:03 +0900745All other files are clustered under a 'Documentation' section.
Shawn O. Pearce795167c2012-05-12 11:20:18 -0700746
747Some optional information from the manifest is extracted and
748displayed as part of the index page, if present in the manifest:
749
750[width="40%",options="header"]
751|===================================================
752|Field | Source Attribute
753|Name | Implementation-Title
754|Vendor | Implementation-Vendor
755|Version | Implementation-Version
756|URL | Implementation-URL
757|API Version | Gerrit-ApiVersion
758|===================================================
759
Edwin Kempinf5a77332012-07-18 11:17:53 +0200760[[deployment]]
Nasser Grainawie033b262012-05-09 17:54:21 -0700761Deployment
762----------
763
Edwin Kempinf7295742012-07-16 15:03:46 +0200764Compiled plugins and extensions can be deployed to a running Gerrit
765server using the link:cmd-plugin-install.html[plugin install] command.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700766
767Plugins can also be copied directly into the server's
768directory at `$site_path/plugins/$name.jar`. The name of
769the JAR file, minus the `.jar` extension, will be used as the
770plugin name. Unless disabled, servers periodically scan this
771directory for updated plugins. The time can be adjusted by
772link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency].
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700773
Edwin Kempinf7295742012-07-16 15:03:46 +0200774For disabling plugins the link:cmd-plugin-remove.html[plugin remove]
775command can be used.
776
Brad Larsond5e87c32012-07-11 12:18:49 -0500777Disabled plugins can be re-enabled using the
778link:cmd-plugin-enable.html[plugin enable] command.
779
David Ostrovskyf86bae52013-09-01 09:10:39 +0200780SEE ALSO
781--------
782
783* link:js-api.html[JavaScript API]
784* link:dev-rest-api.html[REST API Developers' Notes]
785
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700786GERRIT
787------
788Part of link:index.html[Gerrit Code Review]